package com.paultondeur.pv3d.objects
{
	import flash.display.Loader;
	import flash.display.MovieClip;
	
	import org.papervision3d.core.math.Matrix3D;
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.core.proto.MaterialObject3D;
	import org.papervision3d.core.render.data.RenderSessionData;
	import org.papervision3d.materials.MovieMaterial;
	import org.papervision3d.materials.utils.MaterialsList;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Plane;
	
	
	/**
	 * Creates a plane which is always in front of the camera. 
	 * Depending on the position of the current camera and the rotation of this object, it will show you a texture, or a frame within a texture.
	 * 
	 * This is a very useful way to show complex prerendered images to give it a 3D impression. The more images you'll use, the better this impression will be.
	 * The limit of images is set to 360, one for each angle.
	 * 
	 * @author		Paul Tondeur
	 * @version		0.3.3
	 * @date 		28-08-2008
	 */
	public class Sprite3D extends DisplayObject3D
	{
		private var _plane:Plane;
		private var _rotationAxis:String;
		private var _isMovie:Boolean = false;
		private var _materialNum:Number;
		private var _spriteMaterials : Array = new Array();
		
		private var _firstProject:Boolean = true;
		
		static private var toDEGREES :Number = 180/Math.PI;
		static private var toRADIANS :Number = Math.PI/180;
		
		/**
		 * Create a new Sprite3D object
		 * 
		 * @param	materials	A MaterialsList object that contains the MaterialObject3D properties of the object.
		 *						You can add materials in the same order as they should be used. When you add for example 8 materials,
		 * 						then the first material will be used from angle -22.5 till 22.5, the second one from 22.5 till 67.5, etc...
		 * 						You could also add just one material with movieMaterial as it's type. Each frame will cause exactly the same as adding 8 materials.
		 * 						This is especially useful when you have a huge number of angles. This could be up to 360 frames/angles.
		 * 
		 * @param 	width			Optional width of Sprite3D
		 * @param	height			Optional height of Sprite3D
		 * @param 	rotationAxis	The axis (x/y/z) on which your materials in the materialsList are rotating.
		 */
		public function Sprite3D(materials:MaterialsList, width:Number = 250, height:Number = 250, rotationAxis:String = "y")
		{
			super();
			
			this.materials = materials;
			this._rotationAxis = rotationAxis;
			
			/**
			 * Store a local list of materials and order it by value.
			 * This needs to be done, since the materials.materialsByName dictionary contains sometimes wrong ordered data. 
			 * During a test with 2 Sprite3D's using both BitmapFileMaterials it returned the following id's when tracing materials: 16,9,10,11,12,13,14,15.
			 * Storing this information in an Array and ordering it by value solves this problem.
			 */
			for each( var m:MaterialObject3D in materials.materialsByName ) {
				_spriteMaterials.push(m.id);
			}
			/** Sort array, as described above */
			_spriteMaterials.sort(Array.NUMERIC);
			
			buildPlane(width, height);
			
		}
		
		/**
		 * Add a plane to the Sprite3D object
		 */
		private function buildPlane(width:Number = 250, height:Number = 250):void
		{
			_materialNum = 0;
			
			if(materials.materialsByName[_spriteMaterials[_materialNum]] is MovieMaterial && materials.numMaterials == 1){
				_isMovie = true;
			}
			
			_plane = new Plane(materials.materialsByName[_spriteMaterials[_materialNum]], width, height );
			addChild(_plane);
		}
		
		public override function project( parent :DisplayObject3D,  renderSessionData:RenderSessionData ):Number
		{
			/**
			 * First time the sprite3d is projected, it needs to project _plane as well.
			 * If not, we won't see anything happen as soon as we adjust it's matrix.
			 * This only accurs the first time and seems like a pv3d bug
			 */
			if( this._firstProject ) 	{
				_plane.project(this, renderSessionData);
				this._firstProject = false;
			}
			
			/** Update transformation matrix, when dirty transformations are used */
			if( this._transformDirty ) 	updateTransform();
			
			/**	Get rotation matrix of this object and calculate rotation values*/
			var m:Matrix3D = this.transform;
			var rotation:Number3D = new Number3D();
			rotation.x = Math.atan2(-m.n23 , m.n22) * toDEGREES;
			rotation.y = Math.atan2(-m.n31 , m.n33) * toDEGREES;
			rotation.z = Math.atan2(-m.n12 , m.n22) * toDEGREES;
			
			/** Correct _plane and keep it always in front of the camera */
			m = new Matrix3D();
			m = m.copy3x3( this.transform );
			_plane.transform.calculateInverse( m );
			_plane.transform = Matrix3D.multiply3x3( _plane.transform , renderSessionData.camera.transform );
			
			/** Get angle between Sprite3D and the current camera. */
			var distance:Number3D = new Number3D();
			distance.x = renderSessionData.camera.x - this.x;
			distance.y = renderSessionData.camera.y - this.y;
			distance.z = renderSessionData.camera.z - this.z;
			
			/** Angle calculations */
			var angle:Number;
			if(_rotationAxis == "x"){
				angle = ( Math.atan2(distance.z, distance.y) * 180 / Math.PI ) + (rotation.x + 90);
			} else if (_rotationAxis == "z") {
				angle = ( Math.atan2(distance.y, distance.x) * 180 / Math.PI ) + (rotation.z + 90);
			} else {
				angle = ( Math.atan2(distance.z, distance.x) * 180 / Math.PI ) + (rotation.y + 90);
			}
			
			/** set angle between range 0 - 360 */
			angle = angle < 0 ? angle + 360 : angle;
			angle = angle > 360 ? angle - 360 : angle;
			
			var frame:Number;
			if( _isMovie ) {
				/** SWF based textures */
				
				/** materialMovie is null at the firstRun, check this first */
				if( materialMovie != null ){
					frame =  Math.round( angle / ( 360 / materialMovie.totalFrames ) ) + 1;
					materialMovie.gotoAndStop( frame );
				}
			} else {
				/** Material based textures */
				frame = Math.round(angle / (360 / materials.numMaterials));
				
				/**
				 * Correction on frame number. First frame overlaps.
				 * 
				 * When you have for example 8 materials, each material covers 360/8 = 45 degrees.
				 * The first material will cover from 0 -(45/2) = -22.5 = 337.5 to 0 + ( 45/2 ) degrees
				 */
				if(frame == materials.numMaterials || frame < 0 ){
					frame = 0;
				}
				
				/** Prevent changing materials when not necessary */
				if(_materialNum!=frame){
					_materialNum = frame;
					_plane.material = materials.materialsByName[_spriteMaterials[_materialNum]];
				}
			}
			
			return super.project(parent,renderSessionData);
		}
		
		//__________________________________________________________________________________________________GETTERS & SETTERS
		
		/**
		 * Getter to get quick access to this long path
		 * Should be checked frequently, since at start this will return null for a couple of times
		 */
		private function get materialMovie():MovieClip
		{
			return MovieClip( Loader( MovieClip( MovieMaterial( _plane.material ).movie ).getChildAt( _materialNum ) ).content );
		}
		
		/**
		 * Returns a dispayObject3D which could for example be used to add event listeners to.
		 * The displayObject3D is read-only.
		 */
		public function get displayObject3D():DisplayObject3D
		{
			return this._plane;
		}
		
	}
}